package abi

import (
	"encoding/json"
	"errors"
	"fmt"
	"reflect"
	"strings"

	"hundsun.com/hsl/hschain/contract/evm/ext/common"

	"github.com/hyperledger/burrow/execution/evm/abi"
	eabi "hundsun.com/hsl/hschain/contract/evm/ext/abi"
	pbcom "hundsun.com/hsl/hschain/protos/common"
)

// ABI abi操作对象
type ABI struct {
	spec *abi.Spec
}

// LoadFile 从abi文件中载入abi对象
func LoadFile(fpath string) (*ABI, error) {
	spec, err := abi.LoadPath(fpath)
	if err != nil {
		return nil, err
	}
	return &ABI{spec: spec}, nil
}

// NewABI new abi
func NewABI(data string) (*ABI, error) {
	spec, err := abi.ReadSpec([]byte(data))
	if err != nil {
		return nil, err
	}
	return &ABI{spec: spec}, nil
}

// Encode 将调用方法，参数编码成EVM执行格式
func (a *ABI) Encode(methodName string, args map[string]interface{}) ([]byte, error) {
	if methodName == "" {
		if a.spec.Constructor != nil {
			return a.encodeMethod(a.spec.Constructor, args)
		}
		return nil, nil
	}
	method, ok := a.spec.Functions[methodName]
	if !ok {
		return nil, fmt.Errorf("method %s not found", methodName)
	}
	return a.encodeMethod(method, args)
}

func (a *ABI) encodeMethod(method *abi.FunctionSpec, args map[string]interface{}) ([]byte, error) {
	var inputs []interface{}
	for _, input := range method.Inputs {
		v, ok := args[input.Name]
		if !ok {
			return nil, fmt.Errorf("arg name %s not found", input.Name)
		}
		inputs = append(inputs, v)
	}
	out, _, err := a.spec.Pack(method.Name, inputs...)
	return out, err
}

// Encode 用abi数据，调用方法，调用参数(json格式)编码成EVM执行格式
func Encode(abiData, methodName, param string) ([]byte, error) {
	parsedABI, err := NewABI(abiData)
	if err != nil {
		return nil, err
	}
	if param == "" {
		param = "{}"
	}
	paramMp := make(map[string]interface{})
	err = json.Unmarshal([]byte(param), &paramMp)
	if err != nil {
		return nil, err
	}
	inputParam, err := parsedABI.Encode(methodName, paramMp)
	if err != nil {
		return nil, err
	}
	return inputParam, err
}

// DecodedResult 结果解码
type DecodedResult struct {
	Name  string
	Value string
}

// Decode 对调用结果进行解码
func Decode(abiData, methodName string, data []byte) ([]*DecodedResult, error) {
	variables, err := abi.DecodeFunctionReturn(abiData, methodName, data)
	if err != nil {
		return nil, err
	}
	var res []*DecodedResult
	for _, v := range variables {
		res = append(res, &DecodedResult{v.Name, v.Value})
	}
	return res, nil
}

// EventResult 结果解码
type EventResult struct {
	EventName string
	Body      []byte
}

// DecodeEvent 对调用事件进行解码
func DecodeEvent(abiData string, input *pbcom.ContractEvent) (*EventResult, error) {
	pabi, err := eabi.JSON(strings.NewReader(abiData))
	if err != nil {
		fmt.Println("can't generate ABI struct ", err)
		return nil, err
	}
	var topics []common.Hash
	if input.IndexedParamFirst != "" {
		topics = append(topics, common.HexToHash(input.IndexedParamFirst))
	}
	if input.IndexedParamSecond != "" {
		topics = append(topics, common.HexToHash(input.IndexedParamSecond))
	}
	if input.IndexedParamThird != "" {
		topics = append(topics, common.HexToHash(input.IndexedParamThird))
	}
	if len(topics) == 0 {
		return nil, errors.New("input param have no topic")
	}

	event, err := pabi.EventByID(topics[0])
	if err != nil {
		fmt.Println("can't find EventByID ", err)
		return nil, err
	}
	mp := map[string]interface{}{}
	if len(input.GetData()) > 0 {
		if err := pabi.UnpackIntoMap(mp, event.Name, input.GetData()); err != nil {
			return nil, err
		}
	}
	var indexed eabi.Arguments
	for _, arg := range event.Inputs {
		if arg.Indexed {
			indexed = append(indexed, arg)
		}
	}
	err = eabi.ParseTopicsIntoMap(mp, indexed, topics[1:])
	if err != nil {
		return nil, err
	}
	for k, v := range mp { // 去除"0x"
		if reflect.TypeOf(v).Kind() == reflect.TypeOf(common.Hash{}).Kind() {
			mp[k] = fmt.Sprintf("%x", v)
		} else if reflect.TypeOf(v).Kind() == reflect.TypeOf(common.Address{}).Kind() {
			mp[k] = fmt.Sprintf("%x", v)
		}
	}
	data, err := json.Marshal(mp)
	if err != nil {
		return nil, err
	}
	return &EventResult{EventName: event.Name, Body: data}, nil
}

//type ABI struct {
//	abi *eabi.ABI
//}
//
//func LoadFile(fpath string) (*ABI, error) {
//	data, err := util.ReadFile(fpath)
//	if err != nil {
//		return nil, err
//	}
//	parsed, err := eabi.JSON(strings.NewReader(string(data)))
//	if err != nil {
//		fmt.Println("Can't generate ABI struct ", err)
//		return nil, err
//	}
//	return &ABI{abi: &parsed}, nil
//}
//
//func NewABI(data string) (*ABI, error) {
//	parsed, err := eabi.JSON(strings.NewReader(data))
//	if err != nil {
//		fmt.Println("Can't generate ABI struct ", err)
//		return nil, err
//	}
//	return &ABI{abi: &parsed}, nil
//}
//
//func (a *ABI) Encode(methodName string, args map[string]interface{}) ([]byte, error) {
//	if methodName == "" {
//		return a.encodeMethod(&a.abi.Constructor, args)
//	}
//	method, ok := a.abi.Methods[methodName]
//	if !ok {
//		return nil, fmt.Errorf("method %s not found", methodName)
//	}
//	return a.encodeMethod(&method, args)
//}
//
//func (a *ABI) encodeMethod(method *eabi.Method, args map[string]interface{}) ([]byte, error) {
//	var inputs []interface{}
//	for _, input := range method.Inputs {
//		v, ok := args[input.Name]
//		if !ok {
//			return nil, fmt.Errorf("arg name %s not found", input.Name)
//		}
//		inputs = append(inputs, v)
//	}
//	out, err := a.abi.Pack(method.Name, inputs...)
//	return out, err
//}
//
//func (a *ABI) Decode(methodName string, data []byte) (map[string]interface{}, error) {
//	if len(data) == 0 {
//		return nil, fmt.Errorf("method %s data is nil", methodName)
//	}
//	resmp := make(map[string]interface{})
//	err := a.abi.UnpackIntoMap(resmp, methodName, data)
//	if err != nil {
//		return nil, err
//	}
//	return resmp, nil
//}
//
//func Encode(abi, methodName, param string) ([]byte, error) {
//	parsedABI, err := NewABI(abi)
//	if err != nil {
//		return nil, err
//	}
//	paramMp := make(map[string]interface{})
//	err = json.Unmarshal([]byte(param), &paramMp)
//	if err != nil {
//		return nil, err
//	}
//	inputParam, err := parsedABI.Encode(methodName, paramMp)
//	if err != nil {
//		return nil, err
//	}
//	return inputParam, err
//}
//
//func Decode(abi, methodName string, data []byte) (map[string]interface{}, error) {
//	parsedABI, err := NewABI(abi)
//	if err != nil {
//		return nil, err
//	}
//	mp, err := parsedABI.Decode(methodName, data)
//	if err != nil {
//		return nil, err
//	}
//	return mp, err
//}
